require "option_parser"
require "./stats_lib"
require "./expected_output"

module Stats
  @@configs = [
    Config.common_mark_config,
    Config.gfm_config,
  ]

  # Set this to `true` an rerun `--update-files` to ease finding easy strict
  # fixes.
  @@improve_strict = false

  @@scores = Hash(String, Hash(Int32, CompareLevel)).new

  @@section_name_replace = Regex.new("[ \\)\\(]+")

  def self.process_config(test_prefix : String,
                          raw : Bool,
                          update_files : Bool,
                          verbose : Bool,
                          specified_section : String?,
                          verbose_loose : Bool)
    config = @@configs.find! { |c| c.prefix == test_prefix }

    sections = load_common_mark_sections(test_prefix)

    scores = Hash(String, Hash(Int32, CompareLevel)).new

    sections.each do |key, value|
      next if !specified_section.nil? && key != specified_section

      units = [] of DataCase

      value.each do |e|
        result = compare_result(
          config,
          e,
          verbose_fail: verbose,
          verbose_loose_match: verbose_loose,
          extensions: e.extensions
        )

        units << DataCase.new(
          front_matter: result.test_case.to_s,
          input: result.test_case.markdown,
          expected_output: (@@improve_strict && result.compare_level == CompareLevel::Loose) ? result.test_case.html : result.result.not_nil!
        )

        if scores[key]?
          nested_map = scores[key]
        else
          scores[key] = Hash(Int32, CompareLevel).new
          nested_map = scores[key]
        end
        nested_map[e.example] = result.compare_level
      end

      if update_files && units.empty? == false
        file_name = key.downcase.gsub(@@section_name_replace, "_")
        file_name = file_name.rstrip('_')
        file_name = "#{file_name}.unit"
        Dir.mkdir_p Path["spec", test_prefix]
        File.write(Path["spec", test_prefix, file_name], unit_output(units))
      end
    end

    if raw || update_files
      print_raw(test_prefix, scores, update_files)
    end

    if !raw || update_files
      print_friendly(test_prefix, scores, update_files)
    end
  end

  def self.unit_output(cases : Array(DataCase)) : String
    _cases = cases.map do |data_case|
      ">>> #{data_case.front_matter}
#{data_case.input}<<<
#{data_case.expected_output}"
    end

    _cases.join
  end

  def self.pct(value : Int, total : Int, section : String) : String
    "#{value.to_s.rjust(4)} " +
      "of #{total.to_s.rjust(4)} " +
      "- #{(100 * value / total).format('.', "", 1).rjust(5)}%  #{section}"
  end

  def self.print_raw(test_prefix : String, scores : Hash(String, Hash(Int32, CompareLevel)), update_files : Bool) : Nil
    sink : IO::FileDescriptor

    if update_files
      file = get_stats_file(test_prefix)
      puts "Updating #{file.path}"
      sink = file
    else
      sink = STDOUT
    end

    val = JSON.build(" ") do |json|
      json.object do
        scores.each do |section, map|
          json.string section
          json.object do
            map.each do |example, level|
              json.field example, level
            end
          end
        end
      end
    end
    sink.puts val
    sink.flush
    sink.close
  end

  def self.print_friendly(test_prefix : String, scores : Hash(String, Hash(Int32, CompareLevel)), update_files : Bool) : Nil
    total_valid = 0
    total_strict = 0
    total_examples = 0

    sink : IO::FileDescriptor

    if update_files
      path = Path[tool_dir, "#{test_prefix}_stats.txt"]
      puts "Updating #{path}"
      sink = File.open(path, "w+")
    else
      sink = STDOUT
    end

    scores.each do |section, map|
      total = map.values.size
      total_examples += total

      section_strict_count = map.values.count { |val| val == CompareLevel::Strict }
      section_loose_count = map.values.count { |val| val == CompareLevel::Loose }

      section_valid_count = section_strict_count + section_loose_count

      total_strict += section_strict_count
      total_valid += section_valid_count

      sink.puts pct(section_valid_count, total, section)
    end

    sink.puts pct(total_valid, total_examples, "TOTAL")
    sink.puts pct(total_strict, total_valid, "TOTAL Strict")

    sink.flush
    sink.close
  end

  def self.main
    section : String? = nil
    raw = false
    update_files = false
    verbose = false
    verbose_loose = false
    flavor : String? = nil

    cli = OptionParser.new do |parser|
      parser.on("--section SECTION", "Restrict tests to one section") { |val| section = val }
      parser.on("--raw", "raw JSON format") { raw = true }
      parser.on("--update-files", "Update stats files in #{tool_dir}") { update_files = true }
      parser.on("--verbose", "Print details for failures and errors") { verbose = true }
      parser.on("--verbose-loose", "Print details for \"loose\" matches") { verbose_loose = true }
      parser.on("--flavor FLAVOR", "") do |_flavor|
        match = @@configs.find { |c| c.prefix == _flavor }
        if match.nil?
          STDERR.puts "FLAVOR must be one of: " + @@configs.map(&.prefix).join(", ")
          exit 64
        end
        flavor = _flavor
      end
      parser.on("-h", "--help", "Show this help message") do
        puts parser
        exit
      end
      parser.missing_option do |op|
        STDERR.puts "Mission value for option #{op}"
        puts parser
        exit 64
      end
    end

    cli.parse

    if update_files && (raw || verbose || (section != nil))
      STDERR.puts "The `update-files` flag must be used by itself"
      puts cli
      exit 64
    end

    test_prefix = flavor
    unless update_files
      test_prefix = @@configs[0].prefix
    end

    test_prefixes = if test_prefix.nil?
                      @@configs.map(&.prefix)
                    else
                      [test_prefix]
                    end

    test_prefixes.each do |prefix|
      process_config(prefix, raw, update_files, verbose,
        section, verbose_loose)
    end
  end
end

Stats.main
